unit basicAI;

{
  AI mother class, defines the interface.
}

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils,basicio_resources, basicio_city,
  basicio_street, basicio_trade, basicio_data, basicIO_field, basicio_const;

type
  TBuildOrder = record
    Streets     : integer;
    Cities      : integer;
    Inventions  : integer;
    ExtendCities: integer;
  end;


  { TBasicAI }
  TBasicAI = class
    private
      FAIPlayerID: integer;
    public
      property AIPlayerID: integer read FAIPlayerID write FAIPlayerID;
    public
      constructor Create; virtual;
      destructor Destroy; override;
    protected
      function ReturnCity: TCityPosition; virtual; abstract;
      function ReturnStreet: TStreetPosition; virtual; abstract;
      function ReturnAcceptTrade(AOffer: TTradeOffer): boolean; virtual; abstract;
      function ReturnThiefField: TFieldPosition; virtual; abstract;
      function ReturnThiefCity: TCityPosition; virtual; abstract;
      function ReturnThrowRes(AAmount: integer): TResources; virtual; abstract;
      function ReadyToAct: boolean; virtual;
      function WantToTrade: boolean; virtual;
      function ReturnTradeOffer: TTradeOffer; virtual; abstract;
      function WantTobuild(var ABuildOrder: TBuildOrder): boolean; virtual;
      function ReturnExtendCity: TCityPosition; virtual; abstract;
    protected
      function CalcThiefResToThrow: integer;
      function HaveEnoughtRes(AResNeeded: TResources): boolean;
      function IsValidCityPos (APos: TCityPosition): boolean;
      function IsValidStreetPos (APos: TStreetPosition): boolean;
      function CanBuildCity: boolean;
      function CanBuildStreet: boolean;
      function CanExtendCity: boolean;
    protected
      procedure HandleBuilding; virtual;
      function HandleCityBuilding: boolean;
      function HandleStreetBuilding: boolean;
      function HandleExtendCity: boolean;
      function HandleInventions: boolean;
      function CanbuildInvention: boolean;
    public
      procedure Act; virtual;
  end;

const
  EmptyBuildOrder: TBuildOrder = (Streets:0;Cities:0;Inventions:0;ExtendCities:0);


implementation

uses
  SystemManager, StateMachine;

{ TBasicAI }

constructor TBasicAI.Create;
begin
  inherited Create;
end;

destructor TBasicAI.Destroy;
begin
  inherited Destroy;
end;

function TBasicAI.ReadyToAct: boolean;
begin
  result := true;
end;

function TBasicAI.WantToTrade: boolean;
begin
  result := false;
end;

function TBasicAI.WantTobuild(var ABuildOrder: TBuildOrder): boolean;
begin
  result := false;
  ABuildOrder := EmptyBuildOrder;
end;

procedure TBasicAI.Act;
var
  Reacted : boolean;
begin
  Reacted := false;
  while not Reacted do
  begin
    if ReadyToAct then
    begin
      case GlobalSystemManager.IO.Data.Turn of
        GT_Wait:
          begin
            Reacted := true;
          end;
        GT_Build_City:
          begin
            Reacted := HandleCityBuilding;
          end;
        GT_Build_Street:
          begin
            Reacted := HandleStreetBuilding;
          end;
        GT_Build:
          begin
            Reacted := true;
            HandleBuilding;
          end;
        GT_Trade_Self:
          begin
            //HandleTradeRequest;
            Reacted := true;
          end;
        GT_Trade_Enemy:
          begin
            //IO.TradeResult(AI.ReturnAcceptTrade(nil);
            GlobalSystemManager.IO.Output.EndTurn;
          end;
        GT_Thief_ThrowRes:
          begin
            GlobalSystemManager.IO.Output.ThrowRes(ReturnThrowRes(CalcThiefResToThrow));
            Reacted := true;
          end;
        GT_Thief_Field:
          begin
            GlobalSystemManager.IO.Output.MoveThief(ReturnThiefField);
            Reacted := true;
          end;
        GT_Thief_City:
          begin
            GlobalSystemManager.IO.Output.ThiefCity(ReturnThiefCity);
            Reacted := true;
          end;
      end;
    end;
  end;
  GlobalSystemManager.IO.Output.EndTurn;
  GlobalStateMachine.transit(SS_AnalyzeGameState);
end;

function TBasicAI.CalcThiefResToThrow: integer;
var
  ResToThrow: integer;
begin
  ResToThrow:=(GlobalSystemManager.IO.Data.Player[AIPlayerID].Resources.wood+
               GlobalSystemManager.IO.Data.Player[AIPlayerID].Resources.sheeps+
               GlobalSystemManager.IO.Data.Player[AIPlayerID].Resources.dirt+
               GlobalSystemManager.IO.Data.Player[AIPlayerID].Resources.ore+
               GlobalSystemManager.IO.Data.Player[AIPlayerID].Resources.wheat) div 2;
  if ResToThrow > 4 then
  begin
    result := ResToThrow;
  end else
  begin
    result := 0;
  end;
end;

function TBasicAI.HaveEnoughtRes(AResNeeded: TResources): boolean;
begin
  result := (GlobalSystemManager.IO.Data.Player[AIPlayerID].Resources.wood >= AResNeeded.wood) and
            (GlobalSystemManager.IO.Data.Player[AIPlayerID].Resources.Dirt >= AResNeeded.Dirt) and
            (GlobalSystemManager.IO.Data.Player[AIPlayerID].Resources.Sheeps >= AResNeeded.Sheeps) and
            (GlobalSystemManager.IO.Data.Player[AIPlayerID].Resources.Wheat >= AResNeeded.Wheat) and
            (GlobalSystemManager.IO.Data.Player[AIPlayerID].Resources.Ore >= AResNeeded.Ore);
end;

function TBasicAI.IsValidCityPos(APos: TCityPosition): boolean;
var
  i : integer;
  j: integer;
  AStreetToCity: boolean;
begin
  AStreetToCity:= false;
  if GlobalSystemManager.IO.data.Map.CityByPos[APos] = Nil then
  begin
    for i := 0 to 71 do
    begin
      if StreetPosArray[i][0] = APos then
      begin
        if not (GlobalSystemManager.IO.data.Map.StreetByPos[basicIO_const.StreetPosArray[i]] = Nil)and
           (GlobalSystemManager.IO.data.Map.StreetByPos[basicIO_const.StreetPosArray[i]].owner = GlobalSystemManager.IO.data.Player[AIPlayerID]) then
        begin
          AStreetToCity:= true;
          break;
        end;
      end else
      if StreetPosArray[i][1] = APos then
      begin
        if not (GlobalSystemManager.IO.data.Map.StreetByPos[basicIO_const.StreetPosArray[i]] = Nil) and
           (GlobalSystemManager.IO.data.Map.StreetByPos[basicIO_const.StreetPosArray[i]].owner = GlobalSystemManager.IO.data.Player[AIPlayerID]) then
        begin
          AStreetToCity:= true;
          break;
        end;
      end;
    end;
    j := 0;
    for i:= 0 to GlobalSystemManager.IO.data.map.CityCount - 1 do
    begin
      if not (GlobalSystemManager.IO.data.map.CityByID[i].owner = Nil) and ( GlobalSystemManager.IO.data.map.CityByID[i].owner = GlobalSystemManager.IO.Data.Player[AIPlayerID]) then
      begin
        j := j + 1;
      end;
    end;
    if (j<2) then
    begin
      AStreetToCity := true;
    end;
    if AStreetToCity then
    begin
      for i:= 0 to 71 do
      begin
        if basicIO_const.StreetPosArray[i][0] = APos then
        begin
          if not (GlobalSystemManager.IO.data.Map.StreetByPos[basicIO_const.StreetPosArray[i]] = Nil) then
          begin
            if GlobalSystemManager.IO.data.Map.CityByPos[basicIO_const.StreetPosArray[i][1]] = Nil then
            begin
              result:= true;
              exit;
            end;
          end;
        end else
        if basicIO_const.StreetPosArray[i][1] = APos then
        begin
          if not (GlobalSystemManager.IO.data.Map.StreetByPos[basicIO_const.StreetPosArray[i]]  = Nil) then
          begin
            if GlobalSystemManager.IO.data.Map.CityByPos[basicIO_const.StreetPosArray[i][0]] = Nil then
            begin
              result:= true;
              exit;
            end
          end;
       end;
      end;
    end;
  end;
  result := false;
end;

function TBasicAI.IsValidStreetPos(APos: TStreetPosition): boolean;
  function DoesExist: boolean;
  var
    i: integer;
  begin
    result := false;
    for i:=0 to high(StreetPosArray) do
    begin
      if StreetPosArray[i] = APos then
      begin
        result := true;
        break;
      end;
    end;
  end;
  function OntopOfStreet: boolean;
  begin
    result := assigned(GlobalSystemManager.IO.data.Map.StreetByPos[APos])
  end;
  function NextToCity: boolean;
  begin
    result := assigned(GlobalSystemManager.IO.data.Map.CityByPos[APos[0]]) or
              assigned(GlobalSystemManager.IO.data.Map.CityByPos[APos[1]]);
  end;
  function StreetLeadsToPos(ACityPos: TCityPosition): boolean;
  var
    i: integer;
  begin
    result := false;
    for i:=0 to high(StreetPosArray) do
    begin
      if (StreetPosArray[i][0] = ACityPos) or (StreetPosArray[i][1] = ACityPos) then
      begin
        result := result or assigned(GlobalSystemManager.IO.data.Map.StreetByPos[StreetPosArray[i]]);
      end;
    end;
  end;
  function NextToStreet: boolean;
  var
    i: integer;
  begin
    result := false;
    for i:=0 to high(StreetPosArray) do
    begin
      if (StreetPosArray[i][0] = APos[0]) or (StreetPosArray[i][0] = APos[1]) then
      begin
        result := result or StreetLeadsToPos(StreetPosArray[i][0]);
      end;
      if (StreetPosArray[i][1] = APos[0]) or (StreetPosArray[i][1] = APos[1]) then
      begin
        result := result or StreetLeadsToPos(StreetPosArray[i][1]);
      end;
    end;
  end;
begin
  result := DoesExist and (not OntopOfStreet) and (NextToCity or NextToStreet);
end;

function TBasicAI.CanBuildCity: boolean;
begin
  result := HaveEnoughtRes(ResCost_City);
end;


function TBasicAI.CanBuildStreet: boolean;
begin
  result := HaveEnoughtRes(ResCost_Street);
end;

function TBasicAI.CanExtendCity: boolean;
var
  i: integer;
  posFound: boolean;
begin
  result := false;
  for i:=0 to GlobalsystemManager.io.Data.map.CityCount do
  begin
    if GlobalSystemManager.IO.data.Map.CityByID[i].owner = GlobalSystemManager.IO.data.Player[AIPlayerID] then
    begin
      if not GlobalSystemManager.IO.data.Map.CityByID[i].isExtended then
        begin
          posFound:= true;
          break;
        end;
    end;
  end;
  if (HaveEnoughtRes(ResCost_City_extended) and posFound) then {and is able to extend city}
  begin
    result := true;
  end;
end;

procedure TBasicAI.HandleBuilding;
var
  BuildOrder: TBuildOrder;
  i: integer;
begin
  BuildOrder := EmptyBuildOrder;
  while (GlobalSystemManager.IO.Data.Turn = GT_Build) and WantToBuild(BuildOrder) do
  begin
    for i:=0 to BuildOrder.Streets do
    begin
      //GlobalSystemManager.IO.Output.InitBuildStreet;
      while not HandleStreetBuilding do ;
    end;
    for i:=0 to BuildOrder.Cities do
    begin
      //GlobalSystemManager.IO.Output.InitBuildCity;
      while not HandleCityBuilding do ;
    end;
    for i:=0 to BuildOrder.ExtendCities do
    begin
      //GlobalSystemManager.IO.Output.InitExtendCity;
      while not HandleExtendCity do ;
    end;
    for i:=0 to BuildOrder.Inventions do
    begin
      //GlobalSystemManager.IO.Output.InitBuyInvention;
      while not HandleInventions do ;
    end;
  end;
end;

function TBasicAI.HandleCityBuilding: boolean;
var
  i: integer;
  j: integer;
  CityToBuild: TCityPosition;
begin
  j:= 0;
  for i:= 0 to GlobalSystemManager.IO.data.map.CityCount - 1 do
  begin
    if not (GlobalSystemManager.IO.data.map.CityByID[i].owner = Nil) and ( GlobalSystemManager.IO.data.map.CityByID[i].owner = GlobalSystemManager.IO.Data.Player[AIPlayerID]) then
    begin
      j:= j + 1;
    end;
  end;
  result := false;
  CityToBuild := ReturnCity;
  If (IsValidCityPos(CityToBuild) and (HaveEnoughtRes(ResCost_City)or( j < 2))) then
  begin
    GlobalSystemManager.IO.Output.BuildCity(CityToBuild);
    result := true;
  end;
end;

function TBasicAI.HandleStreetBuilding: boolean;
var
  i: integer;
  j: integer;
  StreetToBuild: TStreetPosition;
begin
  j:= 0;
  for i:= 0 to GlobalSystemManager.IO.data.map.StreetCount - 1 do
  begin
    if not (GlobalSystemManager.IO.data.map.StreetByID[i].owner = Nil) and
       ( GlobalSystemManager.IO.data.map.StreetByID[i].owner = GlobalSystemManager.IO.Data.Player[AIPlayerID]) then
    begin
      j:= j + 1;
    end;
  end;
  result := false;
  StreetToBuild := ReturnStreet;
  If ( IsValidStreetPos(StreetToBuild) and (HaveEnoughtRes(ResCost_Street)or(j < 2)) ) then
  begin
    GlobalSystemManager.IO.Output.BuildStreet(StreetToBuild);
    result := true;
  end;
end;

function TBasicAI.HandleExtendCity: boolean;
var
  CityToExtend: TCityPosition;
begin
  result := false;
  CityToExtend:= ReturnExtendCity;
  if (HaveEnoughTRes(ResCost_City_extended)) then
  begin
    GlobalSystemManager.IO.Output.ExtendCity(CityToExtend);
    result:= true;
  end;
end;

function TBasicAI.HandleInventions: boolean;
begin
  result := false;
  if (CanbuildInvention) then
  begin
    GlobalSystemManager.IO.Output.BuildInvention;
    result := true;
  end;
end;

function TBasicAI.CanbuildInvention: boolean;
begin
  result := HaveEnoughtRes(ResCost_Inventions);
end;

{procedure HandleTradeRequest;
begin
  while (IO.Data.Turn = GT_Trade_Self) and AI.WantToTrade do
  begin
    IO.Output.GeneratrTradeOffer(AI.ReturnTradeOffer);
    //pause(10000);
    FIO.Update;
  end;
end; }

end.   

